package com.superkripts.sktools;

import ch.njol.skript.Skript;
import ch.njol.skript.lang.function.Functions;
import ch.njol.skript.lang.function.Signature;
import com.superkripts.sktools.MethodFunctionBuilder.MethodFunctionClassLoader;
import com.superkripts.sktools.annotation.BeforeClass;
import com.superkripts.sktools.annotation.SkriptFunction;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.IllegalPluginAccessException;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;

public class SkriptTools extends JavaPlugin {

    private static SkriptTools instance;
    private final List<Signature<?>> signatures = new ArrayList<>();

    @Override
    public void onEnable() {
        instance = this;
        Skript.registerAddon(this);
        registerFunctions();
        PluginCommand pluginCommand = getCommand("sktools");
        if (pluginCommand != null) {
            pluginCommand.setPermission("sktools.command");
            pluginCommand.setExecutor((sender, command, label, args) -> {
                if (args.length >= 1 && args[0].equalsIgnoreCase("classes")) {
                    sender.sendMessage("正在生成class文件");
                    MethodFunctionClassLoader classLoader = MethodFunctionClassLoader.INSTANCE;
                    File classesFolder = new File(getDataFolder(), "classes");
                    boolean ignored = classesFolder.mkdirs();
                    classLoader.getClasses().forEach((name, data) -> {
                        StringBuilder classFileNameBuilder = new StringBuilder();
                        int lastPoint = name.lastIndexOf('.');
                        (lastPoint == -1
                                ? classFileNameBuilder.append(name)
                                : classFileNameBuilder.append(name, lastPoint + 1, name.length()))
                                .append(".class");
                        String classFileName = classFileNameBuilder.toString();
                        File classFile = new File(classesFolder, classFileName);
                        try (FileOutputStream os = new FileOutputStream(classFile)) {
                            os.write(data);
                            os.flush();
                        } catch (IOException e) {
                            getLogger().log(Level.SEVERE, "生成失败", e);
                            return;
                        }
                        getLogger().info("生成 class 文件: " + classFileName);
                    });
                    sender.sendMessage("生成完成!");
                    return true;
                }
                sender.sendMessage("/" + label + " classes - 生成所有的function类的class文件");
                return true;
            });
            pluginCommand.setTabCompleter((sender, command, label, args) -> {
                if (args.length == 1) {
                    return Collections.singletonList("classes");
                }
                return Collections.emptyList();
            });
        }
    }

    @Override
    public void onDisable() {
        signatures.removeIf(signature -> {
            Functions.unregisterFunction(signature);
            return true;
        });
    }

    private void registerFunctions() {
        Set<String> paths = new HashSet<>();
        // 类扫描
        try (JarFile jarFile = new JarFile(getFile())) {
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String name = jarEntry.getName();
                // 如果是目录则跳过
                if (name.endsWith("/")) {
                    continue;
                }
                if (name.startsWith("com/superkripts/sktools/function/") && name.endsWith(".class")) {
                    paths.add(name);
                }
            }
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
        for (String path : paths) {
            String className = path.substring(0, path.length() - 6).replace('/', '.');
            try {
                Class<?> clazz = Class.forName(className, false, getClassLoader());
                registerFunctions(clazz);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    /**
     * 将该类中带有 {@link SkriptFunction} 注解的静态方法 注册为Skript函数
     * 同时 你可以添加一个或多个带有 {@link BeforeClass} 注解的静态方法 在注册函数前进行额外的一些初始化工作
     *
     * @param clazz 类
     */
    public void registerFunctions(Class<?> clazz) {
        boolean nonStatic = false;
        List<Method> functionMethods = new ArrayList<>();
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(BeforeClass.class) && method.getParameterCount() == 0 && Modifier.isStatic(method.getModifiers())) {
                try {
                    method.setAccessible(true);
                    method.invoke(null);
                } catch (ReflectiveOperationException e) {
                    throw new IllegalStateException(e);
                }
            } else if (method.isAnnotationPresent(SkriptFunction.class)) {
                if (!Modifier.isStatic(method.getModifiers())) {
                    nonStatic = true;
                }
                functionMethods.add(method);
            }
        }
        if (nonStatic) {
            try {
                Object instance = clazz.getConstructor().newInstance();
                functionMethods.forEach(method -> registerFunction(method, instance));
            } catch (ReflectiveOperationException e) {
                throw new IllegalStateException(e);
            }
        } else {
            functionMethods.forEach(this::registerFunction);
        }
    }

    /**
     * 将带有 {@link SkriptFunction} 注解的静态方法 注册为Skript函数
     *
     * @param method 方法
     */
    public void registerFunction(Method method) {
        registerFunction(method, null);
    }

    /**
     * 将带有 {@link SkriptFunction} 注解的静态方法 注册为Skript函数
     *
     * @param method   方法
     * @param instance 函数类实例
     */
    public void registerFunction(Method method, Object instance) {
        SkriptFunction sf = method.getAnnotation(SkriptFunction.class);
        if (sf != null && Modifier.isPublic(method.getModifiers())) {
            if (Modifier.isStatic(method.getModifiers())) {
                registerFunction(method, sf.value(), sf.since(), sf.description(), sf.examples(), sf.requiredPlugin(), null);
            } else if (instance != null) {
                registerFunction(method, sf.value(), sf.since(), sf.description(), sf.examples(), sf.requiredPlugin(), instance);
            }
        }
    }

    /**
     * 将一个静态方法注册为Skript函数
     *
     * @param method         方法
     * @param name           函数名
     * @param since          加入版本
     * @param description    描述
     * @param examples       例子
     * @param requiredPlugin 需要的插件
     */
    public void registerFunction(Method method, String name, String since, String[] description, String[] examples, String[] requiredPlugin) {
        Signature<?> signature = Functions.registerFunction(MethodFunctionBuilder.builder()
                .name(name)
                .since(since)
                .description(description)
                .examples(examples)
                .requiredPlugin(requiredPlugin)
                .instance(null)
                .build(method)).getSignature();
        signatures.add(signature);
    }

    /**
     * 将一个静态方法注册为Skript函数
     *
     * @param method         方法
     * @param name           函数名
     * @param since          加入版本
     * @param description    描述
     * @param examples       例子
     * @param requiredPlugin 需要的插件
     * @param instance       函数类实例(静态方法则不需要)
     */
    public void registerFunction(Method method, String name, String since, String[] description, String[] examples, String[] requiredPlugin, Object instance) {
        MethodFunctionBuilder builder = MethodFunctionBuilder.builder()
                .name(name)
                .since(since)
                .description(description)
                .examples(examples)
                .requiredPlugin(requiredPlugin);
        if (Modifier.isStatic(method.getModifiers())) {
            builder.instance(null);
            Signature<?> signature = Functions.registerFunction(builder.build(method)).getSignature();
            signatures.add(signature);
        } else if (instance != null) {
            builder.instance(instance);
            Signature<?> signature = Functions.registerFunction(builder.build(method)).getSignature();
            signatures.add(signature);
        }
    }

    public static SkriptTools getInstance() {
        if (instance == null) {
            throw new IllegalPluginAccessException();
        }
        return instance;
    }
}
